﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Text;
using System.IO;

using HoldemHand;

namespace HoldemGame
{

    public class Odds
    {
        public virtual double Value{get;set;}
        public long Wins{get;set;}
        public long Ties{get;set;}
        public long Losses{get;set;}
        public long Total{get;set;}

        public double[] My = new double[9];
        public double[] Opp = new double[9];

        public Odds(double value){Value=value;}

        public Odds():this(double.NaN) { }
        public static Odds Invalid = new Odds();

        public double Equity;

        public virtual void Reset()
        {
            Wins = Ties = Losses = Total = 0;
            Value = double.NaN;
            Equity = 0.0;
            for(int i=0;i<9;i++)
                My[i]=Opp[i]=0;
        }

        public override string ToString()
        {
            return string.Format("Equity {0:F3} Total {1} Wins {2:F3} Ties {3:F3}", Equity, Total, (double)Wins/Total, (double)Ties/Total);
        }
    }

    public abstract class OddsCalc
    {
        public virtual Odds CalcOdds(IGameState game, GamePlayer me)
        {
            return new Odds();
        }
    }


    public class OddsVsRandomOneCalc:OddsCalc
    {
        public override Odds CalcOdds(IGameState game, GamePlayer me)
        {
            string pocket = me.PocketCards.ToString();
            string board = game.Table.VisibleBoardCards().ToString();
            return (me.Info.Odds=Calc(pocket, board));
        }

        public virtual Odds Calc(string pocket, string board)
        {
            Odds result = new Odds();
            try
            {
                Hand.HandPlayerOpponentOdds(pocket, board, ref result.My, ref result.Opp);
                result.Value = 0;
                for (int i = 0; i < result.My.Length; i++)
                    result.Value += result.My[i];
                result.Equity = result.Value;
            }
            catch (Exception e)
            {
                result.Value = result.Equity = double.NaN;
            }
            return result;
        }
    }

    public class ProgressEventArgs : EventArgs
    {
        public long Current {get;set;}
        public long Total {get;set;}

        public ProgressEventArgs(long curr, long total)
        {
            Current = curr;
            Total = total;
        }
    }

    public class OddsVsEstCardsCalc:OddsCalc
    {
        private IList<PlayerInfo> _lpi;
        private int[] _current;
        private ulong[][] _avail;
        private NGenerics.DataStructures.Trees.GeneralTree<int> _used;
        private NGenerics.DataStructures.Trees.GeneralTree<int> _node;

        private ulong _boardMask = 0UL;
        private long _total;
        private ulong _deadmask = 0UL;
        private ulong _board;
        private long _playerTotal;
        private uint[] _values;
        private bool _useMonte = true;
        private Random _rnd;

        public long Total { get { return _total;} }
        private EventHandler<ProgressEventArgs> _progress;

        public event EventHandler<ProgressEventArgs> Progress
        {
            add { _progress+=value;}
            remove { _progress-=value;}
        }



        public override Odds CalcOdds(IGameState game, GamePlayer me)
        {
            _lpi = new List<PlayerInfo>();
            foreach (GamePlayer p in game.Players)
            {
                if(p.IsActive && me!=p)
                    _lpi.Add(p.Info);
            }
            if(me.IsActive)
                _lpi.Add(me.Info);


            Calc(_lpi, game.Table.VisibleBoardCards().ToString(), _useMonte);

            return me.Odds;
        }

        public virtual void Calc(IList<PlayerInfo> lpi, string boardCards, bool useMonte)
        {
            _useMonte = useMonte;
            _lpi = lpi;
            _current = new int[_lpi.Count];
            _avail = new ulong[_lpi.Count][];

            _boardMask = Hand.ParseHand(boardCards);

            for (int i = 0; i < _lpi.Count; i++)
            {
                _avail[i] = new ulong[_lpi[i].EstCards.CardSetCount];
                for (int j = 0; j < _lpi[i].EstCards.CardSetCount; j++)
                    _avail[i][j] = Hand.ParseHand(_lpi[i].EstCards.CardSetAt(j).ToString());

                _lpi[i].Odds.Reset();
            }
            _used = new NGenerics.DataStructures.Trees.GeneralTree<int>(0);

            _current = new int[_lpi.Count];
            _values = new uint[_lpi.Count];
            _total = 0;
            _playerTotal = 0;
            _deadmask = _boardMask;
            _rnd = new Random(DateTime.Now.Second);
            Calc(0, _used, 0);
            for (int i = 0; i < _lpi.Count;i++ )
            {
                _lpi[i].Odds.Equity /= _total;
                _lpi[i].Odds.Total = _total;
                _lpi[i].Odds.Value = _lpi[i].Odds.Equity;
            }
        }

        public virtual string DebugState()
        {
            StringBuilder sb = new StringBuilder(string.Format("{0}:{1}:", _total, _playerTotal));
            for(int i=0;i<_current.Length;i++)
            {
                sb.Append(Hand.MaskToString(_avail[i][_current[i]]));
                sb.AppendFormat("[{0:F3}]; ", _lpi[i].Odds.Equity/_total);
            }
            sb.Append("Board: ");
            sb.Append(Hand.MaskToString(_board));
            return sb.ToString();
        }

        public virtual void Calc(int who, NGenerics.DataStructures.Trees.GeneralTree<int> node, int start)
        {
            if(who>=_lpi.Count)
            {
                node = _used;
                bool isfree = false;
                for (int i = 0; i < _current.Length; i++)
                {
                    NGenerics.DataStructures.Trees.GeneralTree<int> child=
                        node.FindChildNode(delegate(int x) { return x == _current[i]; });
                    if (null == child)
                    {
                        child = node.AddNode(_current[i]);
                        isfree = true;
                    }
                    node=child;
                }
                if (!isfree)
                    return;
                /*ulong deadmask = 0ul;
                for (int j = 0; j < _current.Length; j++)
                {
                    deadmask |= _avail[j][_current[j]];
                }*/
                _playerTotal++;
                //if (_playerTotal > 1000)
                   // return;


                IEnumerable<ulong> boardHands = null;
                if (!_useMonte)
                    boardHands = Hand.Hands(_boardMask, _deadmask, 5);
                else
                    boardHands = Hand.RandomHands(_boardMask, _deadmask, 5, 1000);
                foreach (ulong board in boardHands)
                {
                    uint bestValue = 0;
                    int winnersCount=0;
                    for (int j = 0; j < _current.Length; j++)
                    {
                        uint val = Hand.Evaluate(board | _avail[j][_current[j]],7);
                        _values[j]=val;
                        if (val > bestValue)
                        {
                            bestValue = val;
                            winnersCount = 1;
                        }
                        else if (val == bestValue)
                            winnersCount++;
                    }

                    for (int j = 0; j < _current.Length; j++)
                    {
                        if (bestValue == _values[j])
                        {
                            if (winnersCount > 1)
                            {
                                _lpi[j].Odds.Ties++;
                                _lpi[j].Odds.Equity += 1.0 / winnersCount;
                            }
                            else
                            {
                                _lpi[j].Odds.Wins++;
                                _lpi[j].Odds.Equity += 1.0;
                            }
                        }
                        else
                            _lpi[j].Odds.Losses++;
                    }
                    _total++;
                    _board = board;
                    if (_progress != null)
                        _progress(this, new ProgressEventArgs(_total, _total));
                }
                    
                return;
            }
            
            
            
            if (_useMonte && _avail[who].Length>3)
            {
                int nMonte = Math.Min(100,_avail[who].Length);
                for (int iMonte = 0; iMonte < nMonte; iMonte++)
                {
                    int i = _rnd.Next(nMonte);
                    ulong mask = _avail[who][i];
                    if ((mask & _deadmask) == 0)
                    {
                        //if (node.FindChildNode(delegate(int x) { return x == i; }) == null)
                        {
                            _deadmask |= mask;
                            _current[who] = i;
                            Calc(who + 1, node/*node.AddNode(i)*/, 0);
                            //node.Remove(i);
                            _deadmask &= ~mask;
                        }
                    }

                }
            }
            else
            {
                for (int i = start; i < _avail[who].Length; i++)
                {
                    ulong mask = _avail[who][i];
                    if ((mask & _deadmask) == 0)
                    {
                        //if (node.FindChildNode(delegate(int x) { return x == i; }) == null)
                        {
                            _deadmask |= mask;
                            _current[who] = i;
                            Calc(who + 1, null/*node.AddNode(i)*/, 0);
                            //node.Remove(i);
                            _deadmask &= ~mask;
                        }
                    }
                }
            }
        }

    }
}